#include <ed_core/time/datetime.h>

#include <QtCore/QVector>

using namespace ed_core;

// Mid year
const quint32 MID_YEAR_MONTH     = 7;        // Mid year month (starting from 1)
const quint64 MID_YEAR_DAYS      = 5;        // Number of days for the mid-year month.

// Constants used for time calulations.
const quint64 DAYS_PER_YEAR      = 365;      // Number of days in a year, including the mid-year.
const quint64 MONTHS_PER_YEAR    = 13;       // Includes mid-year
const quint64 DAYS_PER_MONTH     = 30;       // Every month shares the same number of days.
const quint64 HOURS_PER_DAY      = 24;       // Every days count 24 hours each.
const quint64 MINUTES_PER_HOUR   = 60;       // Each hour is divided into 60 minutes.
const quint64 SECONDS_PER_MINUTE = 60;       // Each minute counts 60 seconds.

// Seconds conversions.
const quint64 SECONDS_PER_HOUR     = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
const quint64 SECONDS_PER_DAY      = HOURS_PER_DAY  * SECONDS_PER_HOUR;
const quint64 SECONDS_PER_MID_YEAR = MID_YEAR_DAYS  * SECONDS_PER_DAY;
const quint64 SECONDS_PER_MONTH    = DAYS_PER_MONTH * SECONDS_PER_DAY;
const quint64 SECONDS_PER_YEAR     = DAYS_PER_YEAR  * SECONDS_PER_DAY;
const quint64 SECONDS_TO_MID_YEAR  = (MID_YEAR_MONTH-1)*SECONDS_PER_MONTH;

const QVector<QString> MONTH_NAME = QVector<QString>() << "Strassa"       // 01
                                                       << "Veltom"        // 02
                                                       << "Charassa"      // 03
                                                       << "Rua"           // 04
                                                       << "Mawag"         // 05
                                                       << "Gahmil"        // 06
                                                       << "Jour de fête"  // 07 (mid-year)
                                                       << "Raquas"        // 08
                                                       << "Sollus"        // 09
                                                       << "Riag"          // 10
                                                       << "Teayu"         // 11
                                                       << "Borrum"        // 12
                                                       << "Doddul";       // 13

QString DateTime::monthName(quint32 month)
{
  Q_ASSERT(("Invalid month", month >= 1 && month <= 13));
  return MONTH_NAME[month-1]; // Months indexes start from 0.
}

DateTime::DateTime(quint64 timestamp, QObject *parent)
  : QObject(parent)
{
  setTimestamp(timestamp);
}

DateTime::DateTime(quint32 year, quint32 month, quint32 day, quint32 hours, quint32 minutes, quint32 seconds, QObject *parent)
  : QObject(parent)
{
  setDate(year,month,day,hours,minutes,seconds);
}

DateTime::~DateTime()
{
}

quint64 DateTime::timestamp() const
{
  return _timestamp;
}

quint32 DateTime::seconds() const
{
  return secondsSecs();
}

quint32 DateTime::minutes() const
{
  return minutesSecs() / SECONDS_PER_MINUTE;
}

quint32 DateTime::hours() const
{
  return hoursSecs() / SECONDS_PER_HOUR;
}

quint32 DateTime::day() const
{
  quint64 secs = daysSecs();
  return (secs / SECONDS_PER_DAY) + 1;
}

quint32 DateTime::month() const
{
  quint64 secs = monthSecs();

  if (secs < SECONDS_TO_MID_YEAR)
  { // The month is before mid-year => Just divide by the number of seconds per month
    return (secs / SECONDS_PER_MONTH)+1;
  }

  secs -= SECONDS_TO_MID_YEAR;
  if (secs < SECONDS_PER_MID_YEAR)
  { // The month is mid year
    return MID_YEAR_MONTH;
  }

  // The month is after mid-year, compute from the end of the latter
  secs -= SECONDS_PER_MID_YEAR;
  return MID_YEAR_MONTH + (secs / SECONDS_PER_MONTH) + 1;
}

quint32 DateTime::year() const
{
  return _timestamp / SECONDS_PER_YEAR;
}

QString DateTime::monthName() const
{
  return MONTH_NAME[month()];
}

QString formatNumber(quint32 in)
{
  return QString("%1").arg(in,2,10,QChar('0'));
}

QString DateTime::toString(const QString &formatString) const
{
  QString result = formatString;
  return result.replace("YYYY",QString::number(year()))
               .replace("MM"  ,formatNumber(month()))
               .replace("MMM" ,monthName())
               .replace("DD"  ,formatNumber(day()))
               .replace("hh"  ,formatNumber(hours()))
               .replace("mm"  ,formatNumber(minutes()))
               .replace("ss"  ,formatNumber(seconds()));
}

bool DateTime::operator<(const DateTime &other)
{
  return _timestamp < other._timestamp;
}

bool DateTime::operator==(const DateTime &other)
{
  return _timestamp == other._timestamp;
}

void DateTime::setTimestamp(quint64 t)
{
  _timestamp = t;
  emit dateChanged();
}

void DateTime::setDate(quint32 year, quint32 month, quint32 day, quint32 hours, quint32 minutes, quint32 seconds)
{
  Q_ASSERT(("Invalid month",month >= 0 && month <= MONTHS_PER_YEAR));
  Q_ASSERT(("Invalid day", day >= 0 && day <= DAYS_PER_MONTH));
  Q_ASSERT(("Invalid hour", hours < HOURS_PER_DAY));
  Q_ASSERT(("Invalid minute", minutes < MINUTES_PER_HOUR));
  Q_ASSERT(("Invalid second", seconds < SECONDS_PER_MINUTE));
  Q_ASSERT(("Invalid mid-year day", month != MID_YEAR_MONTH || day < MID_YEAR_DAYS));

  // reset timestamp
  _timestamp = 0;

  // compute year
  _timestamp += year * SECONDS_PER_YEAR;

  // compute month
  // Compute everythging before mid_year
  _timestamp += qMin(MID_YEAR_MONTH-1,month-1) * SECONDS_PER_MONTH;

  if (month > MID_YEAR_MONTH)
  { // We are after mid-year, add the rest of the seconds for monthes
    _timestamp += SECONDS_PER_MID_YEAR; // mid-year
    _timestamp += (month-MID_YEAR_MONTH-1)*SECONDS_PER_MONTH; // Rest of the seconds
  }

  // compute day
  _timestamp += (day-1)*SECONDS_PER_DAY;

  // compute hours
  _timestamp += hours*SECONDS_PER_HOUR;

  // compute minutes
  _timestamp += minutes*SECONDS_PER_MINUTE;

  // compute seconds
  _timestamp += seconds;

  emit dateChanged();
}

quint64 DateTime::monthSecs() const
{
  return _timestamp % SECONDS_PER_YEAR;
}

quint64 DateTime::daysSecs() const
{
  quint64 secs = monthSecs();
  quint32 mon = month();
  // FIXME: bad copy pasting of setDate();
  secs -= qMin(MID_YEAR_MONTH-1,mon-1) * SECONDS_PER_MONTH;
  if (mon > MID_YEAR_MONTH)
  { // We are after mid-year, remove the rest of the seconds for monthes
    secs -= SECONDS_PER_MID_YEAR; // mid-year
    secs -= (mon-MID_YEAR_MONTH-1)*SECONDS_PER_MONTH; // Rest of the seconds
  }
  return secs;
}

quint64 DateTime::hoursSecs() const
{
  return daysSecs() % SECONDS_PER_DAY;
}

quint64 DateTime::minutesSecs() const
{
  return hoursSecs() % SECONDS_PER_HOUR;
}

quint64 DateTime::secondsSecs() const
{
  return minutesSecs() % SECONDS_PER_MINUTE;
}
